package bibliotheque;

import java.io.Serializable;
import java.rmi.RemoteException;
import java.util.ArrayList;

import portes.Porte;
import portes.PorteImpl;

public class AlgoJeton extends AlgoAbstract{
	
	// contient la valeur du dernier Jeton reçu
	private Jeton jeton;
	private int successeur;
	
	// Thread gérant les demandes
	private ArrayList<Thread> threads;

	// La classe Jeton (il n'y a qu'un type de message ici)
	private static class Jeton implements Serializable{

		private int nbplaces;

		public Jeton() {
		}
		
		public Jeton(int nbplaces) {
			this.nbplaces = nbplaces;
		}
	}

	private class GestionThread implements Runnable{
		
		// true pour une demande d'entrée, false pour une demande de sortie
		private boolean entree;
		
		public GestionThread(boolean e){
			this.entree = e;
		}
				
		@Override
		public void run(){
			// Traitement de la demande
			if(this.entree){
				// Cas d'une entrée
				if(jeton.nbplaces > 0){
					jeton.nbplaces--;
					((PorteImpl) porte).log("[Porte "+ id + "] entrée de voiture ACCORDÉE, reste " + jeton.nbplaces + " places");
				}
				else{
					((PorteImpl) porte).log("[Porte "+ id + "] entrée de voiture REFUSÉE, plus de places !");
				}
			}
			else{
				// Cas d'une sortie	
                jeton.nbplaces++;
                ((PorteImpl) porte).log("[Porte "+ id + "] SORTIE de voiture, reste " + jeton.nbplaces + " places");
			}
			// dans tous les cas, on fait suivre le jeton
			try {
				sendMessage(id, successeur, jeton);
   			} catch (RemoteException e) {e.printStackTrace();}

		}
	}
	
	public AlgoJeton(Porte p) throws InterruptedException, RemoteException {
		this.porte = p;
		this.id = ((PorteImpl) p).getId();
		this.successeur = (this.id + 1) % Porte.nombreportes;
		this.jeton = new Jeton();
		this.threads = new ArrayList<Thread>();
	}

	/**
	 * Méthode appelée au démarrage par la Porte liée à cette instance d'AlgoJeton
	 */
	@Override
	public void init() throws RemoteException {
		// Au démarrage, seule la première porte crée le jeton et commence à le faire passer
		if(id == 0){
			sendMessage(0, 1, new Jeton(Porte.nombreplacesinitial));
		}	
	}
	
	@Override
	/**
	 * Recevoir et traiter le message transmis par la Porte liée à cette instance d'AlgoJeton.
	 * 
	 * @param from id de la Porte qui envoie le message.
	 * @param to id de la Porte qui reçoit le message (normalement la Porte liée à cette instance d'AlgoJeton !).
	 * @param msg le message reçu, ici de type Jeton
	 * @throws RemoteException
	 */
	public void receiveMessage(int from, int to, Serializable j) throws RemoteException {
			// on mémorise le jeton reçu pour pouvoir le faire passer (éventuellement dans une autre méthode)
			this.jeton = (Jeton) j;
			((PorteImpl) porte).log("[Porte "+ this.id + "] réception de message | from : "+ from + " | to : " + to + " | Type : JETON | Nombre de places : " + ((Jeton) j).nbplaces);
			// si un Thread est en attente de Jeton
			synchronized(threads){
				if(!threads.isEmpty()){
					Thread premier = threads.remove(0);
					premier.start();
				}
				else{
					// sinon on transmet le Jeton à l'identique
					this.sendMessage(this.id, this.successeur, this.jeton);
				}
			}
	}

	@Override
	/**
	 * Envoyer un message à une autre Porte. Le message est transmis à la méthode de même
	 * nom de la Porte liée à cette instance d'AlgoJeton.
	 * 
	 * @param from id de la Porte qui envoie le message (la Porte liée à cette instance d'AlgoJeton).
	 * @param to id de la Porte à qui envoyer le message.
	 * @param msg le message envoyé, ici de type Jeton
	 * @throws RemoteException
	 */
	public void sendMessage(int from, int to, Serializable msg) throws RemoteException {
		Jeton j = (Jeton) msg;
		((PorteImpl) porte).log("[Porte "+ this.id + "] émission de message | from : "+ from + " | to : " + to + " | Type : JETON | Nombre de places : " + j.nbplaces);
		porte.sendMessage(from, to, msg);
	}

	@Override
	/**
	 * Recevoir et traiter une demande d'entrée d'une voiture transmise par la Porte liée à cette instance d'AlgoJeton.
	 * 
	 */
	public void demandeEntree(){
		((PorteImpl) porte).log("[Porte "+ id + "] Demande d'ENTRÉE de voiture");
		// on planifie le traitement de la demande en l'ajoutant à la liste "threads"
		// paramètre à "true" pour une entrée
		synchronized(threads){
			this.threads.add(new Thread(new GestionThread(true)));
		}
	}
	
	@Override
	/**
	 * Recevoir et traiter une demande de sortie d'une voiture transmise par la Porte liée à cette instance d'AlgoJeton.
	 * 
	 */
	public void demandeSortie(){
		((PorteImpl) porte).log("[Porte "+ this.id + "] Demande de SORTIE de voiture");
		// on planifie le traitement de la demande en l'ajoutant à la liste "threads"
		// paramètre à "false" pour une sortie
		synchronized(threads){
			this.threads.add(new Thread(new GestionThread(false)));
		}
	}
}